/*	This work is licensed under Creative Commons GNU LGPL License.

	License: http://creativecommons.org/licenses/LGPL/2.1/
   Version: 0.9
	Author:  Stefan Goessner/2006
	Web:     http://goessner.net/ 
*/
/**
 * A set of transformation rules is written using the object literal notation.
 * So each rule is a name/value pair. The rule name usually is an expression 
 * for accessing an object member. The rule value is either a string or a 
 * function with a single argument, which are evaluated at transformation time.
 * 
 * "name": "transformation string"
 * "name": function(arg){ ... }
 * The transformation string itself can contain one or more expressions 
 * enclosed in curly braces
 * {expr}
 * which always resolve to a string value.
 * 
 * <li> If expr references a rule name, it results in either the transformation 
 * string or the return value of the implicit transformation function of that rule.
 * <li> If expr evaluates to a primitive data type, its value is converted to a string.
 * <li> If expr evaluates to an array/object, each array element/object member 
 * is processed accordingly.
 * <li> The shortcut $ as part of the expr is substituted by the rule name.
 * <li> If expr has the explicit form @name(expr), the function belonging to 
 * the rule name is called and its return value is converted to a string.
 * 
 * The outer JSON object can be accessed using the keyword self.
 * 
 * Rule names for array elements use the syntax name[*]. When using the $ shortcut 
 * in transformation string, the '*' resolves to the actual array index.
 * 
 * Object members, which have no transformation rule assigned and are 
 * not directly or indirectly referenced, as well as expressions evaluating 
 * to undefined don't create output.
 * 
 * See http://goessner.net/articles/jsont/
 * @version 0.9
 * @author Stefan Goessner/2006
 * @license Creative Commons GNU LGPL http://creativecommons.org/licenses/LGPL/2.1/
 * @param {Object} self
 * @param {Object} rules
 */
function jsonT(self, rules) {
   var T = {
      output: false,
      init: function() {
         for (var rule in rules)
            if (rule.substr(0,4) != "self")
               rules["self."+rule] = rules[rule];
         return this;
      },
      apply: function(expr) {
         var trf = function(s){ return s.replace(/{([A-Za-z0-9_\$\.\[\]\'@\(\)]+)}/g, 
                                  function($0,$1){return T.processArg($1, expr);})},
             x = expr.replace(/\[[0-9]+\]/g, "[*]"), res;
         if (x in rules) {
            if (typeof(rules[x]) == "string")
               res = trf(rules[x]);
            else if (typeof(rules[x]) == "function")
               res = trf(rules[x](eval(expr)).toString());
         }
         else 
            res = T.eval(expr);
         return res;
      },
      processArg: function(arg, parentExpr) {
         var expand = function(a,e){return (e=a.replace(/^\$/,e)).substr(0,4)!="self" ? ("self."+e) : e; },
             res = "";
         T.output = true;
         if (arg.charAt(0) == "@")
            res = eval(arg.replace(/@([A-za-z0-9_]+)\(([A-Za-z0-9_\$\.\[\]\']+)\)/, 
                                   function($0,$1,$2){return "rules['self."+$1+"']("+expand($2,parentExpr)+")";}));
         else if (arg != "$")
            res = T.apply(expand(arg, parentExpr));
         else
            res = T.eval(parentExpr);
         T.output = false;
         return res;
      },
      eval: function(expr) {
         var v = eval(expr), res = "";
         if (typeof(v) != "undefined") {
            if (v instanceof Array) {
               for (var i=0; i<v.length; i++)
                  if (typeof(v[i]) != "undefined")
                     res += T.apply(expr+"["+i+"]");
            }
            else if (typeof(v) == "object") {
               for (var m in v)
                  if (typeof(v[m]) != "undefined")
                     res += T.apply(expr+"['"+m+"']");
            }
            else if (T.output)
               res += v;
         }
         return res;
      }
   };
   return T.init().apply("self");
}
